/***
 * Excerpted from "Programming Concurrency on the JVM",
 * published by The Pragmatic Bookshelf.
 * Copyrights apply to this code. It may not be used to create training material, 
 * courses, books, articles, and the like. Contact us if you are in doubt.
 * We make no guarantees that this code is fit for any purpose. 
 * Visit http://www.pragmaticprogrammer.com/titles/vspcon for more book information.
 ***/

import akka.stm.Ref;
import akka.stm.Atomic;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * 
 * A vending machine that has cookies and candy bars.  A user can request
 * a candy bar, a cookie, or both.  The vending machine is replenished every
 * 3 days (3 seconds).
 * 
 */
public class VendingMachine {
	// The maximum of each resource the vending machine can hold
	private final long MAXAMOUNT = 6;
	// Number of cookies in the vending machine
	final Ref<Long> cookies = new Ref<Long>(1L);
	// Number of candy bars in the vending machine
	final Ref<Long> candyBars = new Ref<Long>(1L);
	// Number of times the vending machine has been used
	final Ref<Long> usageCount = new Ref<Long>(0L);
	// Whether or not the vending machine should keep running
	final Ref<Boolean> keepRunning = new Ref<Boolean>(true);
	// how often the vending machine gets replenished
	private final int REPLEN_TIME = 3;
	
	// Threads responsible for replenishing the vending machine
	private static final ScheduledExecutorService replenishTimer = Executors
			.newScheduledThreadPool(1);

	/**
	 * Private constructor of a vending machine
	 */
	private VendingMachine() {
	}

	/**
	 * Schedules the replenished for every 3 seconds
	 */
	private void init() {
		replenishTimer.schedule(new Runnable() {
			public void run() {
				replenish();
				if (keepRunning.get())
					replenishTimer.schedule(this, REPLEN_TIME, TimeUnit.SECONDS);
			}
		}, REPLEN_TIME, TimeUnit.SECONDS);
	}

	/**
	 * Creates and initializes a new vending machine
	 * @return The created vending machine
	 */
	public static VendingMachine create() {
		final VendingMachine vendingMachine = new VendingMachine();
		vendingMachine.init();
		return vendingMachine;
	}

	/**
	 * Stops the vending machine
	 */
	public void stopVendingMachine() {
		keepRunning.swap(false);
	}

	/**
	 * Get the number of cookies left in the vending machine
	 */
	public long getCookiesAvailable() {
		return cookies.get();
	}

	/**
	 * Gets the number of candy bars left in the vending machine
	 */
	public long getCandyBarsAvailable() {
		return candyBars.get();
	}

	/**
	 * Gets the number of times the vending machine has successfully been used
	 */
	public long getUsageCount() {
		return usageCount.get();
	}

	/**
	 * Gets a cookie(s) for the vending machine
	 * @param amount - Number of cookies to get
	 * @return - true if there are enough cookies
	 */
	public boolean getCookie(final long amount) {
		return new Atomic<Boolean>() {
			public Boolean atomically() {
				long currentCookies = cookies.get();
				if (amount > 0 && currentCookies >= amount) {
					cookies.swap(currentCookies - amount);
					usageCount.swap(usageCount.get() + 1);
					return true;
				} else {
					return false;
				}
			}
		}.execute();
	}

	/**
	 * Gets a candy bar(s) for the vending machine
	 * @param amount - Number of candy bars to get
	 * @return - true if there are enough candy bars
	 */
	public boolean getCandyBars(final long amount) {
		return new Atomic<Boolean>() {
			public Boolean atomically() {
				long currentCandyBars = candyBars.get();
				if (amount > 0 && currentCandyBars >= amount) {
					candyBars.swap(currentCandyBars - amount);
					usageCount.swap(usageCount.get() + 1);
					return true;
				} else {
					return false;
				}
			}
		}.execute();
	}

	/**
	 * Get both cookie(s) and candy bar(s) from the vending machine
	 * @param amountCookies - Number of cookies to get
	 * @param amountCandyBars - Number of candyBars to get
	 * @return - true if there are enough cookies and candy bars
	 */
	public boolean getBoth(final long amountCookies, final long amountCandyBars) {

		return new Atomic<Boolean>() {
			public Boolean atomically() {
				
				
				long currentCandyBars = candyBars.get();
				long currentCookies = cookies.get();
				
				if ((amountCookies > 0 && currentCookies >= amountCookies)
						&& (amountCandyBars > 0 && currentCandyBars >= amountCandyBars)) {
					candyBars.swap(currentCandyBars - amountCandyBars);
					cookies.swap(currentCookies - amountCookies);
					usageCount.swap(usageCount.get() + 1);
					
					return true;
					
				} else {
					
					return false;
					
				}
			}
		}.execute();

	}

	/**
	 * Restocks the vending machine to max cookies and candy bars
	 */
	private void replenish() {
		new Atomic<Object>() {
			public Object atomically() {
				long cookieCount = cookies.get();
				long candyBarCount = candyBars.get();

				if (cookieCount < MAXAMOUNT) {
					cookies.swap(MAXAMOUNT);
				}
				if (candyBarCount < MAXAMOUNT) {
					candyBars.swap(MAXAMOUNT);
				}
				System.out.println("VendingMachine was replenished");
				return null;
			}
		}.execute();
	}
}
